package cn.jhz.learn.blog.component.shiro.realm;

import cn.jhz.learn.blog.po.account.AccountRole;
import cn.jhz.learn.blog.po.account.AccountUser;
import cn.jhz.learn.blog.service.security.permission.PermissionService;
import cn.jhz.learn.blog.service.security.permission.RoleService;
import cn.jhz.learn.blog.service.security.permission.UserService;
import com.google.common.collect.Sets;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import org.springframework.beans.factory.annotation.Autowired;

import java.util.Set;
import java.util.stream.Collectors;

/**
 * 使用基于项目环境持久化层中的用户帐户和角色来支持身份验证和授权的Realm接口的简单实现,针对UsernamePasswordToken.<br>
 * 每个帐户条目指定用户的用户名、密码和角色.还可以将角色映射到权限并与用户关联.<br>
 * 不对用户帐号和角色进行存储.<br>
 * @author machine_005
 * @version 1.0
 */
public class SimpleRealm extends AuthorizingRealm {

    private final UserService userService;
    private final PermissionService permissionService;
    private final RoleService roleService;

    @Autowired
    public SimpleRealm(UserService userService, PermissionService permissionService, RoleService roleService) {
        this.userService = userService;
        this.permissionService = permissionService;
        this.roleService = roleService;
    }

    /**
     * 对给定AuthenticationToken实例表示的主题进行身份验证,限定这个Realm只处理UsernamePasswordToken
     * @param token 为身份验证尝试提交的AuthenticationToken
     * @return <tt>true</tt> 如果此域可以对指定令牌表示的主题进行身份验证
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof UsernamePasswordToken;
    }

    /**
     * 返回(application-unique)名称分配给这个<code>Realm</code>。所有配置为单个应用程序的域必须有一个唯一的名称。
     * @return 分配给此领域的(application-unique)名称。
     */
    @Override
    public String getName() {
        return "simpleRealm";
    }

    /**
     * 从基于项目环境持久化层中检索给定主体的AuthorizationInfo.
     * @param principals 应该检索的AuthorizationInfo的主要标识主体.
     * @return 与此主体关联的AuthorizationInfo.
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        Set<String> roles = Sets.newConcurrentHashSet();
        Set<String> permissions = Sets.newConcurrentHashSet();
        AccountUser user = (AccountUser) principals.getPrimaryPrincipal();

        if("machine005@admin.cn".equals(user.getLoginId())){
            /*拥有所有权限*/
            permissions.add("*:*");
            /*添加角色*/
            roles.add("admin");
        }else{
            Set<AccountRole> rolesTemp;
            rolesTemp = roleService.getRolesByUserId(user.getId());
            permissions.addAll(permissionService.getPermissionResourceByRoles(rolesTemp));
            roles.addAll(rolesTemp.stream().map(AccountRole::getName).collect(Collectors.toSet()));
        }

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(permissions);
        info.addRoles(roles);

        return info;
    }

    /**
     * 为给定的身份验证令牌从项目的持久化层中检索身份验证数据.
     * 剩下的校验工作, 由shiro完成.<br>
     * null返回值意味着不能将任何帐户与指定的令牌关联.<br>
     * @param token 包含用户主体和凭据的身份验证令牌,登录时包装的UsernamePasswordToken.
     * @return AuthenticationInfo对象,其中包含只有在查找成功时才会产生的帐户数据(例如,帐户存在且有效,等等).
     * @throws AuthenticationException 如果认证账户被锁定
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        AccountUser user = userService.getUserByLoginId(token.getPrincipal().toString());

        if(user != null)
            /*用户被锁定*/
            if(user.getStatus() != 0)
                /* info对象表示realm登陆比对信息：登陆对象、密码、盐、realm参数 */
                return new SimpleAuthenticationInfo(
                        user, user.getPassword(), ByteSource.Util.bytes(user.getEmail()), getName());
            else
                throw new LockedAccountException("该用户已被锁定,暂时无法登录！");
        else
            return null;
    }
}
